/******************************************************************************
 * Copyright 2022 The AIR Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/

#pragma once
#include <iostream>
#include <memory>
#include <string>

#include <openssl/err.h>

#include "crypto-wrapper/include/cw/common.hpp"

namespace cw {

Digest::Digest(const std::string& name)
    : md_(EVP_get_digestbyname(name.c_str())) {}

Digest::Digest(const EVP_MD* md) : md_(md) {}

std::string Digest::ProcessInputStream(std::istream* is) const {
  if (!is || !*is) {
    std::cerr << "[CW] Invalid stream!" << std::endl;
    return "";
  }
  auto ctx = std::shared_ptr<EVP_MD_CTX>(EVP_MD_CTX_new(), EVP_MD_CTX_free);
  if (!ctx) {
    std::cerr << "[CW] Failed to create EVP_MD_CTX" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  if (0 >= EVP_DigestInit(ctx.get(), md_)) {
    std::cerr << "[CW] Failed to init digest" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }

  char buffer[kBufferSize] = {};
  while (*is) {
    is->read(buffer, kBufferSize);
    if (0 >= EVP_DigestUpdate(ctx.get(), buffer, is->gcount())) {
      std::cerr << "[CW] Failed to digest" << std::endl;
      ERR_print_errors_fp(stderr);
      ERR_clear_error();
      return "";
    }
  }
  unsigned int reslen = kBufferSize;
  if (0 >= EVP_DigestFinal(ctx.get(), reinterpret_cast<unsigned char*>(buffer),
                           &reslen)) {
    std::cerr << "[CW] Failed to finish digest" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  if (reslen != EVP_MD_size(md_)) {
    std::cerr << "[CW] Invalid digest result" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  return {buffer, reslen};
}

std::string Digest::ProcessFile(const std::string& filename) const {
  auto bio = BIO_new_file(filename.c_str(), "rb");
  if (!bio) {
    std::cerr << "[CW] Failed to open file:" << filename << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  return ProcessBio(bio);
}

std::string Digest::ProcessString(const std::string& str) const {
  return ProcessBuffer(str.c_str(), str.length());
}

std::string Digest::ProcessBuffer(const void* buffer, size_t buflen) const {
  auto bio = BIO_new_mem_buf(buffer, static_cast<int>(buflen));
  if (!bio) {
    std::cerr << "[CW] Failed to create mem bio" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  return ProcessBio(bio);
}

std::string Digest::ProcessBio(BIO* bio) const {
  if (!bio) {
    std::cerr << "[CW] BIO is NULL" << std::endl;
    return "";
  }
  auto bio_ptr = std::shared_ptr<BIO*>(&bio, [](BIO** p) { BIO_free_all(*p); });
  auto bio_dgst = BIO_new(BIO_f_md());
  if (!bio_dgst) {
    std::cerr << "[CW] Failed to create digest bio" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  EVP_MD_CTX* ctx = nullptr;
  BIO_get_md_ctx(bio_dgst, &ctx);
  // 先 BIO push 然后再考虑返回的问题
  bio = BIO_push(bio_dgst, bio);
  // 初始化 CTX
  if (!ctx) {
    std::cerr << "[CW] Failed to create get digest ctx" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  if (!EVP_DigestInit_ex(ctx, md_, nullptr)) {
    std::cerr << "[CW] Failed to init digest ctx" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  // 读取数据
  char buffer[kBufferSize] = {};
  while (true) {
    int i = BIO_read(bio, buffer, kBufferSize);
    if (i < 0) {
      std::cerr << "[CW] Failed to read from bio" << std::endl;
      ERR_print_errors_fp(stderr);
      ERR_clear_error();
      return "";
    }
    if (i == 0) {
      break;
    }
  }
  // 获取摘要结果
  auto buflen = BIO_gets(bio, buffer, kBufferSize);
  if (buflen != EVP_MD_size(md_)) {
    std::cerr << "[CW] Invalid digest result" << std::endl;
    ERR_print_errors_fp(stderr);
    ERR_clear_error();
    return "";
  }
  return {buffer, static_cast<size_t>(buflen)};
}

}  // namespace cw
